"""Gene Selector

.. helpdoc::
Often high throughput assays will use multiple probes or markers for the same underlying biological gene.  This can be confusing with one would want to comare the expression of genes to genes and there are several probes in the way.  

This widget is designed to aggregate values together representing the same gene.  The widget must be passed two tables, one representing the expression values and the other the annotations that have mappings of the genes and the probes that represent them in the expression table.

"""

"""<widgetXML>
    <name>Gene Selector</name>
    <icon>default.png</icon>
    <tags>
        <tag>Bioinformatics</tag>
    </tags>
    <summary>Filter Expression data for a set of genes and aggregates the probes into a single value.</summary>
    <details></details>
    <author>
        <authorname>Kyle R Covington</authorname>
        <authorcontact>kyle@red-r.org</authorcontact>
    </author>
</widgetXML>
"""

"""
<name>Gene Selector</name>
<tags>Bioinformatics</tags>
"""
from OWRpy import *
import redR
import redRGUI 
import signals
from libraries.base.qtWidgets.comboBox import comboBox as redRComboBox
from libraries.base.qtWidgets.listBox import listBox as redRListBox
from libraries.base.qtWidgets.filterTable import filterTable as redRFilterTable
from libraries.base.qtWidgets.lineEditHint import lineEditHint as redRSearchBox
from libraries.base.qtWidgets.button import button
from libraries.base.qtWidgets.commitButton import commitButton as redRCommitButton

class geneSelector(OWRpy): 
    settingsList = []
    def __init__(self, **kwargs):
        OWRpy.__init__(self, **kwargs)
        self.data = {}
        self.annotationData = None
        self.expressionData = None
        self.filterList = None
        self.setRvariableNames(['geneData'])
        
        """.. helpdoc::
        The Gene Selector widget can accept data either from an R Data Table or a SQLite Table.  Using an R Table may increase the memory used by the Session and therefore decrease performance of Red-R, large tables in R will also be saved with the session, increasing the size of the saved files.  You might consider making a central location with all annotation tables that you are interested in and placing your tables into that database.  This will reduce the size of the saved files, however, it will make the sessions less "shareable". 
        """
        self.inputs.addInput('id0', 'Annotation Data', [signals.base.RDataFrame, signals.RedRSQLite.SQLiteTable], self.processAnnotations)
        
        self.inputs.addInput('id1', 'Gene Expression Data', signals.base.RDataFrame, self.processExpression)
        
        self.inputs.addInput('filterList', 'Filter List', [signals.base.PythonList, signals.base.RVector], self.processFilterList)

        self.outputs.addOutput('id0', 'Aggregated Expression Data', signals.base.RDataFrame)

        self.probesetIDCol = redRComboBox(self.controlArea, label = 'Probeset ID:')
        self.filterColumn = redRComboBox(self.controlArea, label = 'Filter Column:', callback = self.setFilterColumn)
        self.filterValuesSearch = redRSearchBox(self.controlArea, label = 'Filter Values Search:', callback = self.addFilterValue)
        self.filterValuesSearch.minTextLength = 2
        self.filterByListButton = redRGUI.base.button(self.controlArea, label = 'Filter By List', callback = self.filterByList)
        self.filterValues = redRListBox(self.controlArea, label = 'Filtered Values', callback = self.removeFilterValue)
        self.clearFilteredValuesButton = redRGUI.base.button(self.controlArea, label = "Clear", callback = self.clearFilteredValues)
        self.aggregateFunction = redRComboBox(self.controlArea, label = 'Aggregate Function:', items = [('mean', 'Mean'), ('median', 'Median'), ('min', 'Minimum'), ('max', 'Maximum')])
        
        self.valueTextArea = redRGUI.base.textEdit(self.controlArea, label = 'Values')
        
        redRCommitButton(self.bottomAreaRight, "Commit", callback = self.commitFunction)
    def clearFilteredValues(self):
        self.rSend('id0', None)
        self.filterValues.clear()
    def processAnnotations(self, data):
        if data:
            self.annotationData = data
            if isinstance(self.annotationData, signals.RedRSQLite.SQLiteTable):
                names = self.annotationData.colnames()
            elif isinstance(self.annotationData, signals.base.RDataFrame):
                names = self.R('colnames(as.data.frame(%s))' % self.annotationData.getData(), wantType = 'list')
            self.probesetIDCol.update(names)
            self.filterColumn.update(names)
        else:
            self.annotationData = None
    def processExpression(self, data):
        if data:
            self.expressionData = data
        else:
            self.expressionData = None
    def processFilterList(self, data):
        if data:
            if isinstance(data, signals.base.RVector):
                self.filterList = self.R(str(data.getData()), wantType = redR.LIST)
            else:
                self.filterList = data.getData()
        else:
            self.filterList = None
            
    def filterByList(self):
        self.status.setText('Filtering on list')
        if self.filterList == None:
            self.status.setText('No filter list to process')
            return
        self.filterValues.clear()
        vals = self.getFilterValues()
        usedVals = []
        #print "filtering on ", self.filterList
        for i in [unicode(j) for j in self.filterList]:
            if (unicode(i) in vals) and (unicode(i) not in usedVals):
                self.filterValues.addItem(i,i)
                usedVals.append(unicode(i))
    def getFilterValues(self):
        if isinstance(self.annotationData, signals.RedRSQLite.SQLiteTable):
            newVals = [v[0] for v in self.annotationData.execute('SELECT DISTINCT %s FROM %s' % (self.filterColumn.currentText(), self.annotationData.getData()))]
            
        elif isinstance(self.annotationData, signals.base.RDataFrame):
            myCall = 'as.data.frame(%s)$%s' % (self.annotationData.getData(), self.filterColumn.currentText())
            if self.R('class(%s)' % myCall) == 'factor':
                values = self.R('levels(%s)' % myCall, wantType = redR.LIST)
            elif self.R('class(%s)' % myCall) == 'character':
                values = self.R(myCall, wantType = redR.LIST)
        
            newVals = []
            for v in values:
                if v == None: pass
                elif v in newVals: pass
                else: newVals.append(v)
        return newVals
        
    def setFilterColumn(self):
        
        self.filterValuesSearch.setItems(self.getFilterValues())
    def addFilterValue(self):
        self.filterValues.addItem(unicode(self.filterValuesSearch.text()),unicode(self.filterValuesSearch.text()))
        self.filterValuesSearch.clear()
    def removeFilterValue(self):
        oldItems = self.filterValues.getItems()
        removeItems = self.filterValues.selectedIds()
        newItems = []
        for i in oldItems:
            if i[0] not in removeItems: newItems.append(i)
        self.filterValues.update(newItems)
    def commitFunction(self):
        if self.annotationData == None:
            self.status.setText('No annotation data')
            return
        if self.expressionData == None:
            self.status.setText('No expression data')
            return
            
        ## main goal is to get a list of ID's for each probeset that matches each gene in the selection list
        ## then we want to use the aggregation function and apply that over the subset of genes, the row name is set to the selection name
        ## then we need to aggregate all of these into a table
        
        applyFunctions = []
        targetProbes = self.R('rownames(%s)' % self.expressionData.getData(), wantType = 'list')
        for name in self.filterValues.getItems():
            if isinstance(self.annotationData, signals.RedRSQLite.SQLiteTable):
                probes = [r[0] for r in self.annotationData.execute('SELECT %s FROM %s WHERE %s IN ("%s")' % (self.probesetIDCol.currentId(), self.annotationData.getData(), self.filterColumn.currentText(), name))]
            elif isinstance(self.annotationData, signals.base.RDataFrame):
                probes = self.R('subset(as.data.frame(%s), subset = %s == \'%s\', select = %s)$%s' % (unicode(self.annotationData.getData()), self.filterColumn.currentText(), name, self.probesetIDCol.currentId(), self.probesetIDCol.currentId()), wantType = 'list')
            if type(probes[0]) == list:
                probes = probes[0]
            goodProbes = []
            for p in probes:
                if p not in targetProbes:
                    self.status.setText('Probe %s not represented in expression data.  Please check annotation file.' % p)
                    
                else:
                    goodProbes.append(p)
            if len(goodProbes) == 0:
                self.status.setText('No probes matched between the annotation and expression data')
                return
            applyFunctions.append('apply(data.matrix(%s[c(\'%s\'),]), 2, %s)' % (unicode(self.expressionData.getData()), '\',\''.join([unicode(p) for p in goodProbes]), self.aggregateFunction.currentId()))
        self.R('%s<-as.data.frame(rbind(%s)); rownames(%s)<-make.names(c(\'%s\'))' % (self.Rvariables['geneData'], ','.join(applyFunctions), self.Rvariables['geneData'], '\',\''.join(self.filterValues.getItems())), wantType = 'NoConversion')
        newData = signals.base.RDataFrame(self, data = self.Rvariables["geneData"], checkVal = True) # moment of variable creation, no preexisting data set.  To pass forward the data that was received in the input uncomment the next line.
        #newData.copyAllOptinoalData(self.data)  ## note, if you plan to uncomment this please uncomment the call to set self.data in the process statemtn of the data whose attributes you plan to send forward.
        self.rSend("id0", newData)
        self.valueTextArea.captureOutput(self.Rvariables['geneData'])